package com.cyy.dyeing.common;


import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.cyy.dyeing.dao.TokenDao;
import com.cyy.dyeing.exception.NoAuthenticatedException;
import com.cyy.dyeing.exception.SystemException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.util.Date;

/**
 * @Author: cyy
 * @Date: 2024/7/22
 * 令牌工具类
 */
@Component
@Slf4j
public class TokenUtils {
    //生成 token redis
    //redis 操作字符串的便捷工具对象
    private static StringRedisTemplate stringRedisTemplate;

    /*
        该set方法为实例方法，该方法上加了@Autowired，
        将在令牌工具类的对象被spring容器加载时，自动运行，并注入需要的参数对象
        Spring容器中管理的是实例对象，依赖注入和自动装配只能针对实例方法和实例属性
     */
    @Autowired
    public void setStringRedisTemplate(StringRedisTemplate redisTemplate) {
        TokenUtils.stringRedisTemplate = redisTemplate;

    }


    private static Integer expireTime; //令牌过期时间

    private static String CLAIM_NAME_USERID = "CLAIM_NAME_USERID";//令牌中存放用户账号对应的名字
    private static String CLAIM_NAME_USERNAME = "CLAIM_NAME_USERNAME";

    private static TokenDao tokenDao;

    @Autowired
    public void setTokenDao(TokenDao tokenDao) {
        TokenUtils.tokenDao = tokenDao;
    }



    @Autowired
    public void setEnv(Environment env){

        try {
            expireTime = env.getProperty("clouddyeing.expire-time", Integer.class, 60);
            log.info(expireTime + "令牌过期时间");
        }catch (Exception e){
            e.printStackTrace();
            log.warn("从配置获取令牌过期时间失败");
            expireTime = 60;
        }
    }


    /**
     * 生成token
     * @param currentUser
     * @param securityKey
     * @return
     */
    public static String sign(CurrentUser currentUser, String securityKey){
        Algorithm algorithm = Algorithm.HMAC256(securityKey);
        String token = JWT.create().withClaim(CLAIM_NAME_USERID, currentUser.getUserId())
                .withClaim(CLAIM_NAME_USERNAME, currentUser.getUsername())
                .withExpiresAt(new Date(System.currentTimeMillis() + expireTime * 1000))
                .sign(algorithm);
        return token;
    }

    /**
     * 将当前用户信息以用户密码为密钥生成一个token
     * @param currentUser
     * @param password
     * @return
     */
    public static String loginSign(CurrentUser currentUser, String password){
        String token = sign(currentUser, password);
        //将令牌以键值存入Redis中
        stringRedisTemplate.opsForValue().set(token, token, expireTime);
        return token;
    }

    /**
     * 从客户端传来的令牌中获取当前用户
     * @param clientToken
     * @return
     */
    public static CurrentUser getCurrentUser(String clientToken){
        //1. 判断传来的令牌是否为null
        if(!StringUtils.hasText(clientToken)){
            throw new NoAuthenticatedException("令牌为空! 请先登录!!!");
        }
        //2. 对客户端令牌进行解码，或者解码后的令牌，提取信息
        DecodedJWT decodedJWT = null;
        try {
             decodedJWT = JWT.decode(clientToken);//获取解码后的令牌
        }catch (JWTDecodeException e){
            throw new NoAuthenticatedException("令牌格式错误! 请先登录!!!");
        }
        //3. 解码后的令牌中获取当前用户信息
        String userId = decodedJWT.getClaim(CLAIM_NAME_USERID).asString();
        String username = decodedJWT.getClaim(CLAIM_NAME_USERNAME).asString();
        if(!StringUtils.hasText(userId) || !StringUtils.hasText(username)){
            throw new NoAuthenticatedException("令牌缺失用户信息!!!");
        }
        return new CurrentUser(userId, username);

    }

    /**
     * 通过密钥判断令牌是否合法，合法判断是否过期
     * @param clientToken
     */
    public static void verify(String clientToken){
        //1. 从令牌中获取当前用户信息
        CurrentUser currentUser = getCurrentUser(clientToken);
        //2. 根据当前用户账号从数据中查询出当前用户密码作为密钥进行合法性检验
        String password = null;
        try{
            password = tokenDao.findPasswordByUserId(currentUser.getUserId());
        }catch (Exception e){
            log.error("系统错误:", e);
            throw new SystemException("系统错误");
        }
        //3. 非空判断
        if(!StringUtils.hasText(password)){
            throw new NoAuthenticatedException("没有找到当前用户密钥");
        }
        //4. 验证令牌
        String cacheToken = stringRedisTemplate.opsForValue().get(clientToken);
        if(!StringUtils.hasText(cacheToken)){
            throw new NoAuthenticatedException("令牌过期!请重新登录");
        }
        //5. 根据密钥
    }

    /**
     * 从redis中移除令牌
     * @param clientToken
     */
    public static void removeToken(String clientToken){
        stringRedisTemplate.delete(clientToken);

    }
}
